package com.liao.filter;

import com.alibaba.fastjson.JSON;
import com.liao.config.RestErrorResponse;
import com.liao.domain.LoginUser;
import com.liao.utils.JwtUtil;
import com.liao.utils.RedisCache;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author Mr.M
 * @version 1.0
 * @description 网关认证过虑器
 * @date 2022/9/27 12:10
 */
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {


    @Autowired
    private RedisCache redisCache;

    public static final String X_CLIENT_TOKEN="x-client-token";
    public static final String X_CLIENT_TOKEN_USER="x-client-token-user";

    //白名单
    private static List<String> whitelist = null;

    static {
        //加载白名单
        try (
                InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");
        ) {
            Properties properties = new Properties();
            properties.load(resourceAsStream);
            Set<String> strings = properties.stringPropertyNames();
            whitelist= new ArrayList<>(strings);

        } catch (Exception e) {
            log.error("加载/security-whitelist.properties出错:{}",e.getMessage());
            e.printStackTrace();
        }


    }



    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String requestUrl = exchange.getRequest().getPath().value();
        AntPathMatcher pathMatcher = new AntPathMatcher();
        //白名单放行
        for (String url : whitelist) {
            if (pathMatcher.match(url, requestUrl)) {
                return chain.filter(exchange);
            }
        }


        // 获取token
        String token =  exchange.getRequest().getHeaders().getFirst("token");
        if (!org.springframework.util.StringUtils.hasText(token)) {
            // 放行，后面还有其他过滤器
            return chain.filter(exchange);
        }

        // 解析token
        String id;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            id = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }

        // 从redis中获取用户信息
        String redisKey = "login:" + id;
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        System.out.println(loginUser);
        if (Objects.isNull(loginUser)) {
            throw new RuntimeException("用户未登录");
        }


        // 后面将需要一个Authentication的对象，在这里通过实现类UsernamePasswordAuthenticationToken构造这个对象
        // 选择3个参数的构造器，principal：账号，credentials：密码，authorities：权限
        // 为什么要选择这个构造器呢？因为这个构造器中有，super.setAuthenticated(true); 标识用户为已认证。
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        // 存入SecurityContextHolder
        // 存入需要一个Authentication的对象，在登录的时候也用到过类似的方法。
        // TODO 获取权限信息封装到Authentication
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        ServerHttpRequest.Builder builder = request.mutate();
        // 原始jwt token
        builder.header(X_CLIENT_TOKEN, token);
        //将jwt token中的用户信息传给服务
        String jsonString = JSON.toJSONString(loginUser.getUser());
        builder.header(X_CLIENT_TOKEN_USER, jsonString);
        return chain.filter(exchange.mutate().request(builder.build()).build());

    }

    /**
     * 获取token
     */
    private String getToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().getFirst("token");
        if (StringUtils.isBlank(tokenStr)) {
            return null;
        }
        String token = tokenStr.split(" ")[1];
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return token;
    }





    @Override
    public int getOrder() {
        return 0;
    }
}
